import threading, time, queue

# 生产者和单消费者的简易版生产者消费者模型
# def consume(thread_name, q):
#     while True:
#         time.sleep(2)
#         product = q.get()
#         print("%s consume %s" % (thread_name, product))
#
# def produce(thread_name, q):
#     for i in range(3):
#         product = 'product-' + str(i)
#         q.put(product)
#         print("%s produce %s" % (thread_name, product))
#         time.sleep(1)
#
# q = queue.Queue()
# p = threading.Thread(target=produce, args=("producer",q))
# c = threading.Thread(target=consume, args=("consumer",q))
#
# p.start()
# c.start()
# p.join()

def consume(thread_name, q):
    while True:
        time.sleep(2)
        product = q.get()
        print("%s consume %s" % (thread_name, product))
        q.task_done()
'''
join() 函数是阻塞的。当消费者通过 get() 从队列获取一项任务并处理完成之后，
需要调用且只可以调用一次 task_done()，该方法会给队列发送一个信号，
join()函数则在监听这个信号。可以简单理解为队列内部维护了一个计数器，该计数器标识未完成的任务数，
每当添加任务时，计数器会增加，调用 task_done()时计数器则会减少，直到队列为空。而 join() 就是在监听队列是否为空，一旦条件满足则结束阻塞状态。
'''

# def produce(thread_name, q):
#     for i in range(3):
#         product = 'product-' + str(i)
#         q.put(product)
#         print("%s produce %s" % (thread_name, product))
#         time.sleep(1)
#     q.join()
#
#
# q = queue.Queue()
# p = threading.Thread(target=produce, args=("producer", q))
# c = threading.Thread(target=consume, args=("consumer", q))
# c1 = threading.Thread(target=consume, args=("consumer-1", q))
#
# c.setDaemon(True)
# c1.setDaemon(True)
# p.start()
# c.start()
# c1.start()
#
# p.join()
'''
上述示例中，我们将消费者线程设置为守护线程，这样当主线程结束时消费者线程也会一并结束。然后主线程最后一句 p.join() 又表示主线程必须等待生产者线程结束后才可以结束。

再细看生产者线程的主函数 produce()，该函数中出现了我们上面说过的 q.join() 函数。而 task_done 则是在消费者线程的主函数中调用的。故当生产者线程生产完所有任务后就会被阻塞，
只有当消费者线程处理完所有任务后生产者才会阻塞结束。随着生产者线程的结束，主线程也一并结束，守护线程消费者线程也一并结束，自此所有线程均安全退出。
'''

# TODO 总结
'''
其中最重要的就是通过 task_done() 和 join() 来互相通信。 task_done() 仅仅用来通知队列消费者已完成一个任务，至于任务是什么它毫不关心，它只关心队列中未完成的任务数量。

注意：task_done() 不可以在 put() 之前调用，否则会引发 ValueError: task_done() called too many times。同时在处理完任务后只可以调用一次该函数，否则队列将不能准确计算未完成任务数量。
'''